package com.chat.service.impl;

import static com.chat.enums.ChatExceptionEnum.ACCOUNT_CANNOT_BE_EMPTY;
import static com.chat.enums.ChatExceptionEnum.ACCOUNT_OR_PASSWORD_ERROR;
import static com.chat.enums.ChatExceptionEnum.EMAIL_ALREADY_EXISTS;
import static com.chat.enums.ChatExceptionEnum.EMAIL_AUTHENTICATION_NOT_COMPLETED;
import static com.chat.enums.ChatExceptionEnum.EMAIL_SEND_ERROR;
import static com.chat.enums.ChatExceptionEnum.FREQUENT_REQUESTS_PLEASE_TRY_AGAIN_LATER;
import static com.chat.enums.ChatExceptionEnum.USER_NOT_EXIST;
import static com.chat.enums.ChatExceptionEnum.VERIFICATION_CODE_ERROR;
import static com.chat.enums.ChatExceptionEnum.VERIFICATION_CODE_EXPIRED;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.chat.dao.ChatAccountDao;
import com.chat.domain.ChatAccount;
import com.chat.domain.User;
import com.chat.exception.ChatException;
import com.chat.mapper.ChatAccountMapper;
import com.chat.request.UpdateEmailRequest;
import com.chat.service.AuthenticationService;
import jakarta.annotation.Resource;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class AuthenticationServiceImpl implements AuthenticationService {

    @Resource
    ChatAccountDao chatAccountDao;
    @Resource
    ChatAccountMapper chatAccountMapper;

    @Value("${spring.mail.username}")
    String fromMail;
    @Resource
    StringRedisTemplate stringRedisTemplate;
    @Resource
    JavaMailSender javaMailSender;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (username == null || username.isEmpty()) {
            throw new UsernameNotFoundException(ACCOUNT_CANNOT_BE_EMPTY.getDesc());
        }
        ChatAccount accountByAccountOrEmail = chatAccountDao.getAccountByAccountOrEmail(username);
        if (Objects.isNull(accountByAccountOrEmail)) {
            throw new UsernameNotFoundException(ACCOUNT_OR_PASSWORD_ERROR.getDesc());
        }
        return new User(accountByAccountOrEmail);
    }

    @Override
    public void sendValidateEmail(String email, String sessionId, boolean hasAccount) {
        String key = email + ":" + sessionId + hasAccount;
        checkRateLimit(key);
        ChatAccount chatAccount = chatAccountMapper.selectOne(new LambdaQueryWrapper<ChatAccount>().eq(ChatAccount::getEmail, email));
        if (hasAccount && chatAccount == null) {
            throw new ChatException(USER_NOT_EXIST);
        }
        if (!hasAccount && chatAccount != null) {
            throw new ChatException(EMAIL_ALREADY_EXISTS);
        }
        Random random = new Random();
        int code = random.nextInt(899999) + 100000;
        String text = """
                <br>
                您的验证码是:""" + code + """
                <br>
                """;
        this.sendEmail(fromMail, email, text, code, key);
    }

    @Override
    public void sendValidateEmail(String email, String sessionId, String key) {
        String redisKey = email + ":" + sessionId + key;
        checkRateLimit(key);
        Random random = new Random();
        int code = random.nextInt(899999) + 100000;
        String text = """
                <br>
                您的验证码是:""" + code + """
                <br>
                """;
        this.sendEmail(fromMail, email, text, code, redisKey);
    }


    @Override
    public void validateAndRegisterUser(String username, String password, String email, String code,
            String sessionId) {
        String key = email + ":" + sessionId + "false";
        Long account = chatAccountDao.registerToGenerateAnAccount();
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
            String s = stringRedisTemplate.opsForValue().get(key);
            verifyTheVerificationCodeAndRegisterAnAccount(username, password, email, code, s,
                    account);
        } else {
            throw new ChatException(EMAIL_AUTHENTICATION_NOT_COMPLETED);
        }
    }

    @Override
    public void validateOnly(String email, String code, String redisKey) {
        String key = email + ":" + redisKey;
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
            String s = stringRedisTemplate.opsForValue().get(key);
            if (s == null) {
                throw new ChatException(VERIFICATION_CODE_ERROR);
            }
            if (s.equals(code)) {
                stringRedisTemplate.delete(key);
            } else {
                throw new ChatException(VERIFICATION_CODE_ERROR);
            }
        } else {
            throw new ChatException(EMAIL_AUTHENTICATION_NOT_COMPLETED);
        }
        stringRedisTemplate.opsForValue().set("reset-password", email);
    }


    @Override
    public void resetPassword(String password) {
        String email = stringRedisTemplate.opsForValue().get("reset-password");
        if (email == null) {
            throw new ChatException(EMAIL_AUTHENTICATION_NOT_COMPLETED);
        }
        chatAccountDao.resetPasswordByEmail(email, password);
    }

    @Override
    public boolean updateEmail(UpdateEmailRequest dto) {
        String key = dto.getEmail() + ":" + "updateEmail";
        int update = 0;
        LambdaUpdateWrapper<ChatAccount> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(ChatAccount::getId, dto.getId()).eq(ChatAccount::getEmail, dto.getEmail());
        ChatAccount chatAccount = new ChatAccount();
        chatAccount.setEmail(dto.getNewEmail());
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
            String s = stringRedisTemplate.opsForValue().get(key);
            if (s == null) {
                throw new ChatException(VERIFICATION_CODE_ERROR);
            }
            if (s.equals(dto.getCode())) {
                update = chatAccountMapper.update(chatAccount, wrapper);
            } else {
                throw new ChatException(VERIFICATION_CODE_ERROR);
            }
        }

        return update > 0;
    }

    private void sendEmail(String from, String to, String text, int code, String key) {
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        try {
            sendEmail(from, to, text, code, key, mimeMessage);
        } catch (MessagingException e) {
            throw new ChatException(EMAIL_SEND_ERROR);
        }
    }

    private void sendEmail(String from, String to, String text, int code, String key,
            MimeMessage mimeMessage) throws MessagingException {
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setFrom(from);
        helper.setTo(to);
        helper.setSubject("您的验证码邮件");
        // 设置邮件内容，第二个参数设置是否支持 text/html 类型
        helper.setText(text, true);
        javaMailSender.send(mimeMessage);
        stringRedisTemplate.opsForValue().set(key, String.valueOf(code), 5, TimeUnit.MINUTES);
    }

    private void checkRateLimit(String key) {
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
            Long expire = Optional.ofNullable(stringRedisTemplate.getExpire(key, TimeUnit.SECONDS))
                    .orElse(0L);
            if (expire > 240) {
                throw new ChatException(FREQUENT_REQUESTS_PLEASE_TRY_AGAIN_LATER);
            }
        }
    }

    private void verifyTheVerificationCodeAndRegisterAnAccount(String username, String password,
            String email, String code,
            String s,
            Long account) {
        if (s == null) {
            throw new ChatException(VERIFICATION_CODE_EXPIRED);
        }
        if (s.equals(code)) {
            ChatAccount chatAccount = fillChatAccount(account, username, password, email);
            chatAccountDao.insertAccount(chatAccount);
        } else {
            throw new ChatException(VERIFICATION_CODE_ERROR);
        }
    }

    private ChatAccount fillChatAccount(Long account, String username, String password,
            String email) {
        ChatAccount chatAccount = new ChatAccount();
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        chatAccount.setAccount(account);
        password = bCryptPasswordEncoder.encode(password);
        chatAccount.setUsername(username);
        chatAccount.setPassword(password);
        chatAccount.setEmail(email);
        return chatAccount;
    }
}
